package cloud.keveri.gateway.plugin.call.proxy;

import cloud.keveri.base.KeveriException;
import cloud.keveri.gateway.dto.RpcConfig;
import cloud.keveri.gateway.dto.invoker.ServiceInvoker;
import cn.hutool.core.util.StrUtil;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ConsumerConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.rpc.service.GenericService;

import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;

import static cloud.keveri.gateway.plugin.api.GatewayPluginErrorCodeConstants.ACQUIRE_GENERIC_SERVICE_ERROR;

/**
 * <p>
 * 作者：沈自在
 **/
@Slf4j
public abstract class AbstractRpcCacheSupport {
    /**
     * RPC 相关配置
     */
    private ApplicationConfig applicationConfig;
    private RegistryConfig registryConfig;
    private ConsumerConfig consumerConfig;

    /**
     * cache
     */
    public final LoadingCache<String, ReferenceConfig<GenericService>> caches = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .removalListener((RemovalListener<Object, ReferenceConfig<GenericService>>) notification -> {
                ReferenceConfig<GenericService> config = notification.getValue();
                if (Objects.nonNull(config)) {
                    config.destroy();
                }
            })
            .build(new CacheLoader<>() {
                @Override
                @Nonnull
                public ReferenceConfig<GenericService> load(@Nonnull final String key) {
                    return new ReferenceConfig<>();
                }
            });

    private boolean needUpdateRegistryConfig(final RpcConfig rpcConfig) {
        if (Objects.isNull(registryConfig)) {
            return true;
        }
        return !Objects.equals(rpcConfig.getProtocol(), registryConfig.getProtocol())
                || !Objects.equals(rpcConfig.getRegister(), registryConfig.getAddress());
    }

    /**
     * 初始化配置
     */
    public void init(final RpcConfig rpcConfig) {
        if (Objects.isNull(applicationConfig)) {
            applicationConfig = new ApplicationConfig("keveri_gateway_proxy");
            // 取消 qos
            applicationConfig.setQosEnable(false);
        }
        if (needUpdateRegistryConfig(rpcConfig)) {
            RegistryConfig registryConfigTemp = new RegistryConfig();
            registryConfigTemp.setProtocol(rpcConfig.getProtocol());
            registryConfigTemp.setId("keveri_gateway_proxy");
            registryConfigTemp.setRegister(false);
            registryConfigTemp.setAddress(rpcConfig.getRegister() + "/d08a1086-42b4-4676-a8aa-6ae45fea4aab?username=nacos&password=thr010410");
            Optional.ofNullable(rpcConfig.getGroup()).ifPresent(registryConfigTemp::setGroup);
            registryConfig = registryConfigTemp;
        }
        if (Objects.isNull(consumerConfig)) {
            consumerConfig = new ConsumerConfig();
            consumerConfig.refresh();
            Optional.ofNullable(rpcConfig.getThreadpool()).ifPresent(consumerConfig::setThreadpool);
            Optional.ofNullable(rpcConfig.getCorethreads()).ifPresent(consumerConfig::setCorethreads);
            Optional.ofNullable(rpcConfig.getThreads()).ifPresent(consumerConfig::setThreads);
            Optional.ofNullable(rpcConfig.getQueues()).ifPresent(consumerConfig::setQueues);
        }
    }

    /**
     * 获取 缓存
     */
    protected ReferenceConfig<GenericService> get(final String path) {
        try {
            return caches.get(path);
        } catch (ExecutionException e) {
            throw new KeveriException(ACQUIRE_GENERIC_SERVICE_ERROR);
        }
    }

    /**
     * 无效
     */
    public void invalidate(final String path) {
        caches.invalidate(path);
    }

    public ReferenceConfig<GenericService> initRefN(final ServiceInvoker invoker) {
        try {
            ReferenceConfig<GenericService> referenceConfig = caches.get(invoker.getInvokerPath());
            if (StringUtils.isNoneBlank(referenceConfig.getInterface())) {
                return referenceConfig;
            }
        } catch (ExecutionException ex) {
            log.error("[Keveri-Cloud] 获取 GenericService 缓存时发生错误");
        }
        return build(invoker);
    }

    private ReferenceConfig<GenericService> build(ServiceInvoker invoker) {
        if (Objects.isNull(applicationConfig) || Objects.isNull(registryConfig)) {
            return new ReferenceConfig<>();
        }
        ReferenceConfig<GenericService> reference = buildReference(invoker);
        try {
            Object obj = reference.get();
            if (Objects.nonNull(obj)) {
                log.info("[Keveri-Cloud] buildN init apache dubbo reference success there invoker is :{}", invoker);
                caches.put(invoker.getInvokerPath(), reference);
            }
        } catch (Exception e) {
            log.error("[Keveri-Cloud] 构建 Dubbo Reference 时发生错误", e);
        }
        return reference;
    }

    @SuppressWarnings("all")
    private ReferenceConfig<GenericService> buildReference(ServiceInvoker invoker) {
        ReferenceConfig<GenericService> reference = new ReferenceConfig<>();

        reference.setGeneric("true");
        reference.setAsync(true);
        reference.setApplication(applicationConfig);
        reference.setRegistry(registryConfig);
        reference.setConsumer(consumerConfig);

        reference.setInterface(invoker.getInterfaceClazz());
        reference.setProtocol("dubbo");
        reference.setCheck(false);
        reference.setLoadbalance("gray");

        Map<String, String> parameters = new HashMap<>(2);
        parameters.put("dispatcher", "direct");
        reference.setParameters(parameters);

        String version = invoker.getVersion();
        if (StrUtil.isNotBlank(version)) {
            reference.setVersion(version);
        }

        return reference;
    }
}
